/*
 * Copyright 2011 Clean GWT
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleangwt.bootstrap.client.ui;

import com.cleangwt.bootstrap.client.SystemMessages;
import com.cleangwt.bootstrap.client.style.Btn;
import com.cleangwt.bootstrap.client.style.Styles;
import com.cleangwt.user.client.Elements;
import com.cleangwt.user.client.ui.Button;
import com.cleangwt.user.client.ui.RawWidget;
import com.cleangwt.user.client.ui.ValueCallback;
import com.cleangwt.user.shared.Option;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Modals are streamlined, but flexible, dialog prompts with the minimum required functionality and
 * smart defaults.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 * @see http://getbootstrap.com/javascript/#modals
 */
public class Modal extends ComplexPanel {

  private static final String DEFAULT_DONE_VALUE = "done";

  private static final String DEFAYLT_STYLENAME = "modal";
  private static final String DIALOG_STYLE = "modal-dialog";
  private static final String BODY_STYLE = "modal-body";
  private static final String CONTENT_STYLE = "modal-content";
  private static final String FOOTER_STYLE = "modal-footer";
  private static final String HEADER_STYLE = "modal-header";
  private static final String TITLE_STYLE = "modal-title";

  /**
   * Activates the given element to be a modal toggle.
   * 
   * @param e the element
   * @param target the modal
   */
  public static void activate(Element e, String target) {
    e.setAttribute(DataModel.DATA_TOGGLE, DataModel.TOGGLE_MODAL);
    e.setAttribute(DataModel.DATA_TARGET, target);
  }

  /**
   * Shows a confirm dialog with the given message and callback.
   * 
   * @param message the message.
   * @param callback the callback to execute on 'done' button is clicked
   */
  public static void confirm(String message, ValueCallback<String> callback) {
    option(SystemMessages.Impl.confirm(), new RawWidget(DivElement.TAG, message), callback);
  }

  /**
   * Hides the given modal.
   * 
   * @param e the modal element
   */
  public static native void hide(Element e) /*-{
                                            $wnd.jQuery(e).modal('hide');
                                            }-*/;

  /**
   * Shows a message dialog with the given message.
   * 
   * @param message the message
   */
  public static void message(String message) {
    message(message, null);
  }

  /**
   * Shows a message dialog with the given title, message and callback.
   * 
   * @param title the title
   * @param message the message
   * @param callback the callback to execute on 'done' button is clicked
   */
  public static void message(String message, ValueCallback<String> callback) {
    Button btnDone = new Button(SystemMessages.Impl.done());
    btnDone.getElement().setAttribute("value", DEFAULT_DONE_VALUE);
    option(SystemMessages.Impl.message(), new RawWidget(DivElement.TAG, message),
        new Button[] {btnDone}, callback);

  }

  private static native void onHidden(Element e, Command func) /*-{
                                                               $wnd.jQuery(e).on('hidden.bs.modal', function() {
                                                               func.@com.google.gwt.user.client.Command::execute()();
                                                               })
                                                               }-*/;

  private static native void onHide(Element e, Command func) /*-{
                                                             $wnd.jQuery(e).on('hide.bs.modal', function() {
                                                             func.@com.google.gwt.user.client.Command::execute()();
                                                             })
                                                             }-*/;

  private static native void onShow(Element e, Command func) /*-{
                                                             $wnd.jQuery(e).on('show.bs.modal', function() {
                                                             func.@com.google.gwt.user.client.Command::execute()();
                                                             })
                                                             }-*/;

  private static native void onShown(Element e, Command func) /*-{
                                                              $wnd.jQuery(e).on('shown.bs.modal', function() {
                                                              func.@com.google.gwt.user.client.Command::execute()();
                                                              })
                                                              }-*/;

  /**
   * Shows an option pane with the given options.
   * 
   * @param title the title
   * @param contentWidget the content widget
   * @param options the options
   * @param callback the callback to execute on any option button is clicked
   */
  public static void option(String title, final Widget contentWidget, Button[] options,
      final ValueCallback<String> callback) {
    final Modal pane = new Modal();
    pane.setTitle(title);
    pane.setBody(contentWidget);
    for (final Button option : options) {
      pane.addOption(option);
      option.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
          pane.hide();
          String value = option.getElement().getAttribute("value");
          if (callback != null && value != null && value.length() != 0) {
            callback.onValueCallback(value);
          }
        }
      });
    }
    pane.show();
    Modal.onShown(pane.getElement(), new Command() {

      @Override
      public void execute() {
        if (contentWidget instanceof Focusable) {
          ((Focusable) contentWidget).setFocus(true);
        }
      }
    });

  }

  /**
   * Shows an option pane with the given options.
   * 
   * @param title the title
   * @param contentWidget the content widget
   * @param callback the callback to execute on any option button is clicked
   */
  public static void option(String title, Widget contentWidget, final ValueCallback<String> callback) {
    Button btnDone = new Button();
    Button btnCancel = new Button(SystemMessages.Impl.cancel());
    // btnDone.setStyleBase(Buttons.PRIMARY_STYLE);
    Btn.style(Btn.PRIMARY_STYLE, btnDone);
    btnDone.setHTML(SystemMessages.Impl.done());
    btnDone.getElement().setAttribute("value", DEFAULT_DONE_VALUE);
    option(title, contentWidget, new Button[] {btnCancel, btnDone}, callback);
  }

  /**
   * Shows an option pane with the given options.
   * 
   * @param contentWidget the content widget
   * @param options the options
   * @param callback the callback to execute on any option button is clicked
   */
  public static void option(Widget contentWidget, Option[] options, ValueCallback<String> callback) {
    Button[] buttons = new Button[options.length];
    int i = 0;
    for (Option option : options) {
      buttons[i] = new Button(option.getName());
      buttons[i].getElement().setAttribute("value", option.getValue());
      i++;
    }
    option(SystemMessages.Impl.option(), contentWidget, buttons, callback);
  }

  /**
   * Shows an option pane with the given options.
   * 
   * @param title the title
   * @param input the input
   * @param callback the callback to take the value of input
   */
  public static <T, W extends Widget & HasValue<T>> void input(String title, final W input,
      final ValueCallback<T> callback) {
    Button btnDone = new Button();
    Button btnCancel = new Button(SystemMessages.Impl.cancel());
    // btnDone.setStyleBase(Buttons.PRIMARY_STYLE);
    Btn.style(Btn.PRIMARY_STYLE, btnDone);
    btnDone.setHTML(SystemMessages.Impl.done());
    btnDone.getElement().setAttribute("value", DEFAULT_DONE_VALUE);

    option(title, input, new Button[] {btnCancel, btnDone}, new ValueCallback<String>() {

      @Override
      public void onValueCallback(String option) {
        callback.onValueCallback(input.getValue());
      }
    });
  }

  /**
   * Shows the given modal.
   * 
   * @param e the modal element
   */
  public static native void show(Element e) /*-{
                                            $wnd.jQuery(e).modal('show');
                                            }-*/;

  private Element titleElement = Document.get().createHElement(4);
  private Element bodyElement = Document.get().createDivElement();
  private Element footerElement = Document.get().createDivElement();
  private Widget contentWidget;
  private boolean callbcakHandlerInitialized;

  /**
   * Creates a modal.
   */
  public Modal() {
    this(true, true);
  }

  /**
   * Creates an modal with the given 'backdrop' and 'dismissable'.
   * 
   * @param backdrop whether to close the modal on click
   * @param dismissable whether to add a default close button
   */
  public Modal(boolean backdrop, boolean dismissable) {
    Element modal = Document.get().createDivElement();
    Element dialog = Document.get().createDivElement();
    Element content = Document.get().createDivElement();
    Element header = Document.get().createDivElement();

    String titleId = Document.get().createUniqueId();
    this.setElement(modal);
    modal.setClassName(DEFAYLT_STYLENAME);
    modal.addClassName(Styles.FADE);
    modal.setAttribute("aria-labeledby", titleId);
    modal.setAttribute("aria-hidden", "true");
    Roles.getDialogRole().set(modal);
    modal.appendChild(dialog);

    dialog.setClassName(DIALOG_STYLE);
    dialog.appendChild(content);

    content.setClassName(CONTENT_STYLE);
    content.appendChild(header);
    content.appendChild(bodyElement);
    content.appendChild(footerElement);

    header.setClassName(HEADER_STYLE);
    if (dismissable) {
      Element close = Document.get().createPushButtonElement();
      header.appendChild(close);
      close.setClassName(Styles.CLOSE);
      close.setAttribute(DataModel.DATA_DISMISS, DataModel.TOGGLE_MODAL);
      close.setAttribute("aria-hidden", "true");
      close.setInnerHTML("&times;");
    }

    header.appendChild(titleElement);

    titleElement.setId(titleId);
    titleElement.setClassName(TITLE_STYLE);

    bodyElement.setClassName(BODY_STYLE);
    footerElement.setClassName(FOOTER_STYLE);

    if (!backdrop) {
      modal.setAttribute(DataModel.DATA_DROPBACK, "static");
    }
  }

  /**
   * Adds a dismiss button with the given html.
   * 
   * @param html the html
   */
  public void addDismissButton(String html) {
    Element button = Document.get().createPushButtonElement();
    button.setClassName("btn btn-default");
    button.setInnerHTML(html);
    button.setAttribute(DataModel.DATA_DISMISS, DataModel.TOGGLE_MODAL);
    footerElement.appendChild(button);
  }

  /**
   * Add an button option.
   * 
   * @param btn the button option
   */
  public void addOption(Button btn) {
    add(btn, footerElement);
  }

  /**
   * Adds an option.
   * 
   * @param text the option text
   * @param optionValue the option value
   */
  public void addOption(Button btn, final String optionValue) {
    if (optionValue != null) {
      btn.getElement().setAttribute("value", optionValue);
    }
    add(btn, footerElement);
  }

  /**
   * Hides the modal.
   */
  public void hide() {
    hide(getElement());
  }

  /**
   * Sets body content with the given html.
   * 
   * @param html the html
   */
  public void setBody(String html) {
    this.bodyElement.setInnerHTML(html);
  }

  /**
   * Sets body content with the given widget.
   * 
   * @param w the widget
   */
  public void setBody(Widget w) {
    if (contentWidget != null) {
      this.remove(contentWidget);
    }
    this.add(w, bodyElement);
  }

  /**
   * Sets the dialog width.
   * 
   * @param width the width
   */
  public void setDialogWidth(int width) {
    Element e = Elements.getByClass(getElement(), DIALOG_STYLE);
    e.getStyle().setWidth(width, Unit.PX);
  }

  /**
   * Sets the title.
   * 
   * @param title the title
   */
  public void setTitle(String title) {
    titleElement.setInnerHTML(title);
  }

  /**
   * Shows the modal at root panel.
   */
  public void show() {
    RootPanel.get().add(this);
    show(getElement());
    Modal.onHidden(getElement(), new Command() {

      @Override
      public void execute() {
        RootPanel.get().remove(Modal.this);
      }
    });
  }
}